﻿using System;
using System.Collections.Generic;
using System.Data.Services.Providers;
using System.Linq;
using umbraco.DataServices.Linq;
using umbraco.DataServices.Query;

namespace umbraco.DataServices
{
    public class ServiceQueryProvider : IDataServiceQueryProvider
    {
        private UmbracoContext _dataSource;
        private readonly ServiceMetadata _metadata;

        /// <summary>Constructor.</summary>
        public ServiceQueryProvider(ServiceMetadata metadata, UmbracoContext context) {
            _metadata = metadata;
            _dataSource = context;
        }

        #region IDataServiceQueryProvider Members

        /// <summary>Sets or gets the data source.</summary>
        public object CurrentDataSource
        {
            get { return _dataSource; }
            set {
                if (_dataSource != null)
                    throw new InvalidOperationException("CurrentDataSource should only be set once.");
                _dataSource = (UmbracoContext)value;  
            }
        }

        public object GetOpenPropertyValue(object target, string propertyName) {
            throw new NotSupportedException("Open types are not supported.");
        }

        public IEnumerable<KeyValuePair<string, object>> GetOpenPropertyValues(object target) {
            throw new NotSupportedException("Open types are not supported.");
        }

        /// <summary>Gets a value of a declared property for a resource.</summary>
        /// <param name="target">The target resource to get a value of the property from.</param>
        /// <param name="resourceProperty">The name of the property to get.</param>
        /// <returns>The value of the property.</returns>
        /// <remarks>The returned value's type should match the type declared in the resource's resource type.</remarks>
        public object GetPropertyValue(object target, ResourceProperty resourceProperty) {
            if (target == null)
                throw new ArgumentNullException("target");
            if (target == null)
                throw new ArgumentNullException("resourceProperty");
            if (target is UmbracoNode == false)
                throw new Exception("GetPropertyValue Target Needs To Implement UmbracoNode");
            var targetNode = (UmbracoNode)target;
            if (resourceProperty.CustomState != null)
                if (resourceProperty.CustomState is PropertyIdentifier)
                    return targetNode.GetPropertyValue(((PropertyIdentifier)resourceProperty.CustomState).PropertyAlias); //See CustomState Because Property Isnt Named The Same As What's In The Properties Bag
            return targetNode.GetPropertyValue(resourceProperty.Name);
        }

        /// <summary>Returns a query which can be used to retriev resource from the specified resource set.</summary>
        /// <param name="resourceSet">The resource set to get the query for.</param>
        /// <returns>An <see cref="IQueryable"/> which will be used to get resources from the specified resource set.</returns>
        /// <remarks>The data service will use the LINQ to build the actual query required for the resource set. It's up to this provider
        /// to return an <see cref="IQueryable"/> which can handle such queries. If the resource set is not recognized by the provider it should return null.</remarks>
        public IQueryable GetQueryRootForResourceSet(ResourceSet resourceSet) {
            if (resourceSet.Name == "Node")
                return UmbracoQueryProvider.ElevateQuery(_dataSource.Select().AsQueryable()); //Return Generic Set
            return UmbracoQueryProvider.ElevateQuery(_dataSource.Select(resourceSet.ResourceType.Name).AsQueryable());
        }

        /// <summary>Returns a resource type for the specified resource.</summary>
        /// <param name="target">The target resource for which to determine its type.</param>
        /// <returns>The <see cref="ResourceType"/> of the specified resource.</returns>
        /// <remarks>The method should throw if the resource is not recognized. If it returns null the data service will throw instead.</remarks>
        public ResourceType GetResourceType(object target)
        {
            if (target is UmbracoNode == false)
                throw new Exception("GetResourceType Needs To Implement UmbracoNode");

            var targetNode = (UmbracoNode)target;
            return _metadata.Types.Single(p => p.Name == targetNode.DocumentType);
        }

        /// <summary>Invokes the specified service operation.</summary>
        /// <param name="serviceOperation">The service operation to invoke.</param>
        /// <param name="parameters">The parameters for the service operation. Their types will match the types of the declared parameters for the service operation.</param>
        /// <returns>The result of the service operation.
        /// If the service operation is declared to return void, this method should return null.
        /// Otherwise the method should return object of the type declared as the return type for the service operation.</returns>
        public object InvokeServiceOperation(ServiceOperation serviceOperation, object[] parameters) {
            throw new NotSupportedException("Service operations are not supported.");
        }

        /// <summary>Gets a value indicating whether null propagation is required in expression trees.</summary>
        /// <remarks>If this is set to true than the generated expression trees will contain conditionals to check for nulls when trying to access properties on objects.
        /// If this is set to false, no such checks will be present in the tree and the data service assumes that the query provider can handle
        /// accesses to properties on instances which are null (what happens in that case is up to the provider, but it is assumed that value of such property is also null).</remarks>
        public bool IsNullPropagationRequired {
            get { return false; }
        }

        #endregion
    }
}
